from datetime import datetime

from sqlalchemy import *
from sqlalchemy.ext.activemapper import ActiveMapper, column, \
                                    one_to_many, one_to_one, many_to_many
from sqlalchemy.ext.assignmapper import assign_mapper

from turbogears import identity 
from turbogears.database import metadata, session

from docutils.core import publish_parts

post_table = Table("posts", metadata,
    Column("id", Integer, primary_key=True),
    Column("title", Unicode(50)),
    Column("content", Unicode),
    Column("post_date", DateTime, default=datetime.now),
    Column("is_published", Boolean, default=False),
)
class Post(object):
    def __init__(self, *args, **kw):
        for k,v in kw.iteritems():
            if hasattr(self, k):
                setattr(self, k, v)
    
    def _get_html_content(self):
        return publish_parts(self.content,
                writer_name="html")["html_body"]
    html_content = property(_get_html_content)
    
    def _get_comment_count(self):
        return select([func.count(comment_table.c.id)],
                    comment_table.c.post_id == self.id).scalar()
    comment_count = property(_get_comment_count)

    def _get_tags(self):
        return self._tags

    def _set_tags(self, data):
        """Updates the Post's tags, adds missing Tag objects and
        keeps the relationships in sync.

        @param data Either a comma separated string or a list of strings"""
        if isinstance(data, basestring):
            # Turn the data into a list
            data = data.split(",")

        # Create a normalised list of the new tag names
        new_tags = [Tag.normalise_tag_name(tag) for tag in data]

        # Create a list of the current tag names
        old_tags = [t.name for t in self._tags]

        # Sort the lists
        new_tags.sort()
        old_tags.sort()
        if not old_tags == new_tags:
            # Create a transaction for this little section
            trans = session.create_transaction()

            # Clear our old tags
            self._tags = []

            for tag_name in new_tags:
                tag_obj = Tag.get_by(_name=tag_name)
                if not tag_obj:
                    # We need to create this tag in the DB
                    tag_obj = Tag(tag_name)
                self._tags.append(tag_obj)

            # We're done.  Commit the transaction
            trans.commit()

    tags = property(_get_tags, _set_tags)

comment_table = Table("comments", metadata,
    Column("id", Integer, primary_key=True),
    Column("post_id", Integer, ForeignKey("posts.id")),
    Column("author_name", Unicode(255), nullable=False),
    Column("author_email", String(255), nullable=False),
    Column("author_url", String(255)),
    Column("comment_date", DateTime, default=datetime.now),
    Column("content", Unicode),
)
class Comment(object):
    def __init__(self, post, author_name, author_email,
                content, author_url=None, comment_date=None):
        self.post = post
        self.author_name = author_name
        self.author_email = author_email
        self.content = content
        if author_url:
            self.author_url = author_url
        if comment_date:
            self.comment_date = comment_date
            
tag_table = Table("tags", metadata,
    Column("id", Integer, primary_key=True),
    Column("name", Unicode(100), unique=True),
)
class Tag(object):
    def __init__(self, name):
        self.name = name
        
    @classmethod
    def normalise_tag_name(cls, tag_name):
        return tag_name.lower().strip().replace(' ', '+')
        
    def _set_name(self, name):
        self._name = Tag.normalise_tag_name(name)
        
    def _get_name(self):
        return self._name
        
    name = property(_get_name, _set_name)
    
tag_post_table = Table("tag_post", metadata,
    Column("posts_id", Integer, ForeignKey("posts.id")),
    Column("tag_id", Integer, ForeignKey("tags.id")),
)
    

assign_mapper(session.context, Post, post_table,
    properties={
        "_tags": relation(Tag, secondary=tag_post_table, backref="posts", lazy=False)
    }
)
assign_mapper(session.context, Comment, comment_table,
    properties={
        "post": relation(Post, backref="comments")
    }
)
assign_mapper(session.context, Tag, tag_table,
    properties={
        "_name": tag_table.c.name
    }
)



class Visit(ActiveMapper):
    class mapping:
        __table__ = "visit"
        visit_key = column(String(40), primary_key=True)
        created = column(DateTime, nullable=False, default=datetime.now)
        expiry = column(DateTime)

    def lookup_visit(cls, visit_key):
        return Visit.get(visit_key)
    lookup_visit = classmethod(lookup_visit)

# tables for SQLAlchemy identity
user_group = Table("user_group", metadata, 
                      Column("user_id", Integer,
                              ForeignKey("tg_user.user_id"),
                              primary_key=True),
                      Column("group_id", Integer,
                              ForeignKey("tg_group.group_id"),
                              primary_key=True))

group_permission = Table("group_permission", metadata,
                            Column("group_id", Integer,
                                    ForeignKey("tg_group.group_id"),
                                    primary_key=True),
                            Column("permission_id", Integer,
                                ForeignKey("permission.permission_id"),
                                    primary_key=True))


class VisitIdentity(ActiveMapper):
    class mapping:
        __table__ = "visit_identity"
        visit_key = column(String(40), # foreign_key="visit.visit_key",
                          primary_key=True)
        user_id = column(Integer, foreign_key="tg_user.user_id", index=True)


class Group(ActiveMapper):
    """
    An ultra-simple group definition.
    """
    class mapping:
        __table__ = "tg_group"
        group_id = column(Integer, primary_key=True)
        group_name = column(Unicode(16), unique=True)
        display_name = column(Unicode(255))
        created = column(DateTime, default=datetime.now)

        users = many_to_many("User", user_group, backref="groups")
        permissions = many_to_many("Permission", group_permission,
                                   backref="groups")


class User(ActiveMapper):
    """
    Reasonably basic User definition. Probably would want additional attributes.
    """
    class mapping:
        __table__ = "tg_user"
        user_id = column(Integer, primary_key=True)
        user_name = column(Unicode(16), unique=True)
        email_address = column(Unicode(255), unique=True)
        display_name = column(Unicode(255))
        password = column(Unicode(40))
        created = column(DateTime, default=datetime.now)

        groups = many_to_many("Group", user_group, backref="users")

    def permissions(self):
        perms = set()
        for g in self.groups:
            perms = perms | set(g.permissions)
        return perms
    permissions = property(permissions)


class Permission(ActiveMapper):
    class mapping:
        __table__ = "permission"
        permission_id = column(Integer, primary_key=True)
        permission_name = column(Unicode(16), unique=True)
        description = column(Unicode(255))

        groups = many_to_many("Group", group_permission,
                              backref="permissions")
